package com.java.simple.study.juc.p8;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

/**
 * @author zhoujunlin
 * @date 2022年10月19日 22:30
 * @desc
 */
@Slf4j
public class TestWordCount {

    static final String ALPHA = "abcdefghijklmnopqrstuvwxyz";


    public static void main(String[] args) {
        //generateWords2File();

        // 结论：HashMap线程不安全
        /*countTemplate(() -> new HashMap<String, Integer>(),
                (map, words) -> {
                    for (String word : words) {
                        Integer counter = map.get(word);
                        int newValue = Objects.isNull(counter) ? 1 : counter + 1;
                        map.put(word, newValue);
                    }
                });*/

        // 结论： ConcurrentHashMap单个方法是原子性的  但是多个方法的组合不是原子性的
        /*countTemplate(() -> new ConcurrentHashMap<String, Integer>(),
                (map, words) -> {
                    for (String word : words) {
                        Integer counter = map.get(word);
                        int newValue = Objects.isNull(counter) ? 1 : counter + 1;
                        map.put(word, newValue);
                    }
                });*/

        // 线程安全
        /*countTemplate(() -> new ConcurrentHashMap<String, LongAdder>(),
                (map, words) -> {
                    for (String word : words) {
                        map.computeIfAbsent(word, k-> new LongAdder()).increment();
                    }
                });*/

        // 线程安全
        countTemplate(() -> new ConcurrentHashMap<String, Integer>(),
                (map, words) -> {
                    for (String word : words) {
                        map.merge(word, 1, Integer::sum);
                    }
                });

    }

    /**
     * 统计模板方法
     * 正确返回应该是map中每个字母的数量都是200
     *
     * @param supplier 提供一个map  key是字母 value是字母个数
     * @param consumer 消费  用于填充map
     * @param <V>
     */
    private static <V> void countTemplate(Supplier<Map<String, V>> supplier,
                                          BiConsumer<Map<String, V>, List<String>> consumer) {
        Map<String, V> counterMap = supplier.get();
        // 生成26个线程并发统计
        List<Thread> ts = new ArrayList<>(ALPHA.length());
        for (int i = 1; i <= ALPHA.length(); i++) {
            int idx = i;
            Thread thread = new Thread(() -> {
                List<String> words = readFromFile(idx);
                consumer.accept(counterMap, words);
            });
            ts.add(thread);
        }
        ts.forEach(Thread::start);
        ts.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(counterMap);
    }

    /**
     * 读取第i个文件
     *
     * @param i
     * @return
     */
    public static List<String> readFromFile(int i) {
        List<String> words = new ArrayList<>();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(
                new FileInputStream("tmp/" + i + ".txt")
        ))) {
            while (true) {
                String word = in.readLine();
                if (StrUtil.isBlank(word)) {
                    break;
                }
                words.add(word);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return words;
    }

    /**
     * 生成200个随机字母到26个文件中
     */
    private static void generateWords2File() {
        int length = ALPHA.length();
        int count = 200;
        List<String> list = new ArrayList<>(length * count);
        for (int i = 0; i < length; i++) {
            char ch = ALPHA.charAt(i);
            for (int j = 0; j < count; j++) {
                list.add(String.valueOf(ch));
            }
        }
        Collections.shuffle(list);
        for (int i = 0; i < length; i++) {
            try (PrintWriter out = new PrintWriter(new OutputStreamWriter(
                    new FileOutputStream("tmp/" + (i + 1) + ".txt")
            ))) {
                String collect = String.join("\n", list.subList(i * count, (i + 1) * count));
                out.println(collect);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}
